/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/*
 * SpriteLibraryFrame.java
 *
 * Created on Apr 24, 2010, 12:23:57 PM
 */

package thetaeditor;
import java.util.*;
import java.io.*;
import org.w3c.dom.*;
import javax.xml.parsers.*;
import javax.swing.JFileChooser;
import javax.swing.filechooser.FileFilter;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;

/**
 *
 * @author garrett
 */
public class SpriteLibraryFrame extends javax.swing.JFrame
        implements Statusable {
    private LinkedList<Sprite> sprites = new LinkedList<Sprite>();
    private File lastSave = null;
    private boolean relativeFile = false;

    /** Creates new form SpriteLibraryFrame */
    public SpriteLibraryFrame() {
        initComponents();
    }

    public void setStatus(final String text) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                statusLabel.setText(text);
            }
        });
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        jScrollPane1 = new javax.swing.JScrollPane();
        spriteList = new javax.swing.JList();
        jLabel1 = new javax.swing.JLabel();
        add = new javax.swing.JButton();
        remove = new javax.swing.JButton();
        edit = new javax.swing.JButton();
        statusLabel = new javax.swing.JLabel();
        jMenuBar1 = new javax.swing.JMenuBar();
        jMenu1 = new javax.swing.JMenu();
        newItem = new javax.swing.JMenuItem();
        openItem = new javax.swing.JMenuItem();
        saveItem = new javax.swing.JMenuItem();
        saveAsItem = new javax.swing.JMenuItem();
        jSeparator1 = new javax.swing.JSeparator();
        importItem = new javax.swing.JMenuItem();
        exportItem = new javax.swing.JMenuItem();
        jSeparator2 = new javax.swing.JSeparator();
        quitItem = new javax.swing.JMenuItem();
        jMenu2 = new javax.swing.JMenu();
        aboutItem = new javax.swing.JMenuItem();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("Sprite Library");

        jScrollPane1.setViewportView(spriteList);

        jLabel1.setText("Sprite Library");

        add.setText("Add");
        add.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                addActionPerformed(evt);
            }
        });

        remove.setText("Remove");
        remove.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                removeActionPerformed(evt);
            }
        });

        edit.setText("Edit");
        edit.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                editActionPerformed(evt);
            }
        });

        statusLabel.setText("Idle");

        jMenu1.setText("File");

        newItem.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_N, java.awt.event.InputEvent.CTRL_MASK));
        newItem.setMnemonic('n');
        newItem.setText("New");
        newItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                newItemActionPerformed(evt);
            }
        });
        jMenu1.add(newItem);

        openItem.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_O, java.awt.event.InputEvent.CTRL_MASK));
        openItem.setMnemonic('o');
        openItem.setText("Open");
        openItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                openItemActionPerformed(evt);
            }
        });
        jMenu1.add(openItem);

        saveItem.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_S, java.awt.event.InputEvent.CTRL_MASK));
        saveItem.setMnemonic('s');
        saveItem.setText("Save");
        saveItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                saveItemActionPerformed(evt);
            }
        });
        jMenu1.add(saveItem);

        saveAsItem.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_S, java.awt.event.InputEvent.SHIFT_MASK | java.awt.event.InputEvent.CTRL_MASK));
        saveAsItem.setMnemonic('a');
        saveAsItem.setText("Save As");
        saveAsItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                saveAsItemActionPerformed(evt);
            }
        });
        jMenu1.add(saveAsItem);
        jMenu1.add(jSeparator1);

        importItem.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_I, java.awt.event.InputEvent.CTRL_MASK));
        importItem.setMnemonic('i');
        importItem.setText("Import");
        importItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                importItemActionPerformed(evt);
            }
        });
        jMenu1.add(importItem);

        exportItem.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_E, java.awt.event.InputEvent.CTRL_MASK));
        exportItem.setMnemonic('e');
        exportItem.setText("Export");
        exportItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                exportItemActionPerformed(evt);
            }
        });
        jMenu1.add(exportItem);
        jMenu1.add(jSeparator2);

        quitItem.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_Q, java.awt.event.InputEvent.CTRL_MASK));
        quitItem.setMnemonic('q');
        quitItem.setText("Quit");
        quitItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                quitItemActionPerformed(evt);
            }
        });
        jMenu1.add(quitItem);

        jMenuBar1.add(jMenu1);

        jMenu2.setText("Help");

        aboutItem.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_F1, 0));
        aboutItem.setText("About");
        aboutItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                aboutItemActionPerformed(evt);
            }
        });
        jMenu2.add(aboutItem);

        jMenuBar1.add(jMenu2);

        setJMenuBar(jMenuBar1);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 378, Short.MAX_VALUE)
                    .addComponent(jLabel1)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(edit)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(add)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(remove)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 201, Short.MAX_VALUE)
                        .addComponent(statusLabel)))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jLabel1)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 235, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(edit)
                    .addComponent(add)
                    .addComponent(remove)
                    .addComponent(statusLabel)))
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void addActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addActionPerformed
            sprites.add(new Sprite());
            updateList();
    }//GEN-LAST:event_addActionPerformed

    private void removeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_removeActionPerformed
        int[] selected = spriteList.getSelectedIndices();
        if (selected.length == 0) return;
        if (selected.length == 1) {
            sprites.remove(selected[0]);
            updateList();
            return;
        }
        for (int i = selected.length-1; i >= 0; i--) {
            sprites.remove(i);
        }
        updateList();
    }//GEN-LAST:event_removeActionPerformed

    private void editActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_editActionPerformed
        int selected = spriteList.getSelectedIndex();
        if (selected == -1 || selected >= sprites.size()) return;
        edit(selected);
    }//GEN-LAST:event_editActionPerformed

    private void newItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newItemActionPerformed
        if (sprites.size() > 0) {
            int confirm = JOptionPane.showConfirmDialog(this,
                    "Start new library?");
            if (confirm != JOptionPane.YES_OPTION)
                return;
        }
        sprites.clear();
        updateList();
        lastSave = null;
        relativeFile = false;
        setStatus("Ready");
    }//GEN-LAST:event_newItemActionPerformed

    private void openItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_openItemActionPerformed
        userOpen();
    }//GEN-LAST:event_openItemActionPerformed

    private void saveItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_saveItemActionPerformed
        if (lastSave == null) {
            userSaveAs();
            return;
        }
        if (relativeFile) {
            exportLast();
        } else {
            saveLast();
        }
    }//GEN-LAST:event_saveItemActionPerformed

    private void saveAsItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_saveAsItemActionPerformed
        userSaveAs();
    }//GEN-LAST:event_saveAsItemActionPerformed

    private void importItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_importItemActionPerformed
        userImport();
    }//GEN-LAST:event_importItemActionPerformed

    private void quitItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_quitItemActionPerformed
        if (sprites.size() == 0)
            System.exit(0);
        int confirm = JOptionPane.showConfirmDialog(this,
                "Are you sure you want to quit?");
        if (confirm == JOptionPane.YES_OPTION)
            System.exit(0);
    }//GEN-LAST:event_quitItemActionPerformed

    private void aboutItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_aboutItemActionPerformed
        JOptionPane.showMessageDialog(this,
                "<html>This editor was written by Garrett Malmquist.<br>" +
                "It was designed for the CODE Club Project \"theta-12\"." +
                "<br><br>A note about saving projects:<br>" +
                "Saving a project normally (with File->Save) does not<br>" +
                "copy any image files, it just links to them. To copy the<br>" +
                "files into their own directory, use File->Export. If you<br>" +
                "File->Open an exported .xml file, going to File->Save will<br>" +
                "automatically re-export it. This means that you can continue<br>" +
                "adding stuff to exported files. If you don't want to keep<br>" +
                "re-exporting each time, you can always go to File->Save As.<br>" +
                "<br>If none of that made any" +
                " sense, sorry. I tried =P"
                );
    }//GEN-LAST:event_aboutItemActionPerformed

    private JFileChooser expChooser;
    private void exportItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportItemActionPerformed
        ArrayList<Sprite> alsprites = new ArrayList<Sprite>();
        for (Sprite s : this.sprites)
            alsprites.add(s.clone());

        if (expChooser == null) {
            expChooser = new JFileChooser();
            expChooser.setFileFilter(new XMLFilter());
            expChooser.setAcceptAllFileFilterUsed(false);
        }
        int choice = expChooser.showSaveDialog(this);
        if (choice != JFileChooser.APPROVE_OPTION) return;
        File sel = expChooser.getSelectedFile();
        if (sel == null) return;

        if (sel.exists()) {
            int overwrite = JOptionPane.showConfirmDialog(
                    this, "Overwrite existing file?");
            if (overwrite != JOptionPane.YES_OPTION)
                return;
        }

        Exporter exp = new Exporter(this, alsprites, sel.getName());
        exp.export(sel.getParentFile().getAbsolutePath());
    }//GEN-LAST:event_exportItemActionPerformed

    private void saveLast() {
        setStatus("Saving file...");
        try {
            PrintWriter out = new PrintWriter(lastSave);
            out.println(storeSprites());
            out.close();
            setStatus("Saved.");
        } catch (IOException e) {
            JOptionPane.showMessageDialog(this,
                    "I couldn't save the file =(",
                    "Error", JOptionPane.ERROR_MESSAGE);
            setStatus("Error saving file.");
        }
    }
    private void exportLast() {
        setStatus("Exporting file...");
        ArrayList<Sprite> alsprites = new ArrayList<Sprite>();
        for (Sprite s : this.sprites)
            alsprites.add(s.clone());
        Exporter exp = new Exporter(this, alsprites, lastSave.getName());
        exp.export(lastSave.getParentFile().getAbsolutePath());
    }

    private JFileChooser xmlChooser;

    private void userSaveAs() {
        if (xmlChooser == null) {
            xmlChooser = new JFileChooser();
            xmlChooser.setFileFilter(new XMLFilter());
            xmlChooser.setAcceptAllFileFilterUsed(false);
        }
        int choice = xmlChooser.showSaveDialog(this);
        if (choice != JFileChooser.APPROVE_OPTION) {
            return;
        }
        File file = xmlChooser.getSelectedFile();
        if (!file.getName().toLowerCase().endsWith(".xml")) {
            file = new File(file.getAbsolutePath() + ".xml");
        }
        if (file.exists()) {
            int confirm = JOptionPane.showConfirmDialog(this,
                    "Overwrite existing file?");
            if (confirm != JOptionPane.YES_OPTION) {
                return;
            }
        }
        setStatus("Writing file");
        try {
            PrintWriter out = new PrintWriter(file);
            out.println(storeSprites());
            out.close();

            lastSave = file;
            relativeFile = false;
        } catch (IOException e) {
            JOptionPane.showMessageDialog(this,
                    "I couldn't save the file =(",
                    "Error", JOptionPane.ERROR_MESSAGE);
        }
        setStatus("File Saved.");
    }

    public void userOpen() {
        if (xmlChooser == null) {
            xmlChooser = new JFileChooser();
            xmlChooser.setFileFilter(new XMLFilter());
            xmlChooser.setAcceptAllFileFilterUsed(false);
        }
        int choice = xmlChooser.showOpenDialog(this);
        if (choice != JFileChooser.APPROVE_OPTION) {
            return;
        }
        File file = xmlChooser.getSelectedFile();
        openSprites(file.getAbsolutePath());
        if (sprites.size() > 0)
            lastSave = file;
    }

    public void userImport() {
        if (xmlChooser == null) {
            xmlChooser = new JFileChooser();
            xmlChooser.setFileFilter(new XMLFilter());
            xmlChooser.setAcceptAllFileFilterUsed(false);
        }
        int choice = xmlChooser.showOpenDialog(this);
        if (choice != JFileChooser.APPROVE_OPTION) {
            return;
        }
        importSprites(xmlChooser.getSelectedFile().getAbsolutePath());
    }

    private Hashtable<Sprite, SpriteFrame> openFrames
            = new Hashtable<Sprite, SpriteFrame>();
    private synchronized void edit(int index) {
        Sprite spr = sprites.get(index);
        SpriteFrame sf = openFrames.get(spr);
        if (sf != null && sf.isVisible()) {
            return; // There is already a SpriteFrame up for this Sprite.
        }
        sf = new SpriteFrame(spr);
        sf.setLocation(getX() + getWidth(), getY());
        sf.setVisible(true);
    }

    private void updateList() {
        Sprite[] ss = new Sprite[sprites.size()];
        for (int i = 0; i < ss.length; i++)
            ss[i] = sprites.get(i);
        spriteList.setListData(ss);
    }

    /**
     * Clears the current list of sprites, and reads new sprites from
     * the file path
     * @param path
     */
    private void openSprites(String path) {
        sprites.clear();
        loadSprites(getDir(path), loadFile(path), true);
    }

    /**
     * Adds the sprites in the given filepath to the library
     * @param path
     */
    private void importSprites(String path) {
        loadSprites(getDir(path), loadFile(path), false);
    }

    private String getDir(String path) {
        int sep = path.lastIndexOf(File.separator);
        if (sep == -1) return ".";
        return path.substring(0, sep);
    }

    private String loadFile(String path) {
        File file = new File(path);
        if (!file.exists() || file.isDirectory()) return null;
        setStatus("Loading file...");
        BufferedReader in = null;
        try {
            in = new BufferedReader(new FileReader(file));
        } catch (IOException e) {
            return null;
        }
        StringBuffer sb = new StringBuffer(1000);
        String reads = null;
        do {
            try {
                reads = in.readLine();
            } catch (IOException e) {
                break;
            }
            if (reads == null) break;
            if (sb.length() > 0) sb.append("\n");
            sb.append(reads);
        } while (reads != null);
        System.out.println("File read.");
        return sb.toString();
    }

    private String storeSprites() {
        StringBuffer sb = new StringBuffer(1000);
        sb.append("<spritelist relative=\"0\">\n");
        for (Sprite s : sprites) {
            sb.append("\t");
            sb.append(s.store().trim().replaceAll("\n", "\n\t"));
            sb.append("\n");
        }
        sb.append("</spritelist>");
        return sb.toString();
    }

    private void loadSprites(String base, String data, boolean saveSettings) {
        try {
            setStatus("Parsing xml");
            DocumentBuilderFactory factory =
                    DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Reader reader = new CharArrayReader(data.toCharArray());
            Document doc = builder.parse(new org.xml.sax.InputSource(reader));
            Node node = doc.getDocumentElement();

            NamedNodeMap atts = node.getAttributes();
            boolean relative = atts.getNamedItem("relative")
                    .getNodeValue().equals("1");

            if (!relative) base = "";

            if (saveSettings) {
                this.relativeFile = relative;
            }

            NodeList children = node.getChildNodes();
            for (int i = 0; i < children.getLength(); i++) {
                Node child = children.item(i);
                if (!child.hasAttributes()) continue;
                if (child.getAttributes().getNamedItem("name") == null)
                    continue;
                sprites.add(Sprite.read(base, null, child));
            }

        } catch (Exception e) {
            System.err.println("Error parsing sprites: " + e);
            setStatus("Error parsing sprites");
        }
        setStatus("Updating gui...");
        updateList();
        setStatus("Finished loading sprites.");
    }

    /**
    * @param args the command line arguments
    */
    public static void main(String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new SpriteLibraryFrame().setVisible(true);
            }
        });
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JMenuItem aboutItem;
    private javax.swing.JButton add;
    private javax.swing.JButton edit;
    private javax.swing.JMenuItem exportItem;
    private javax.swing.JMenuItem importItem;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JMenu jMenu1;
    private javax.swing.JMenu jMenu2;
    private javax.swing.JMenuBar jMenuBar1;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JSeparator jSeparator1;
    private javax.swing.JSeparator jSeparator2;
    private javax.swing.JMenuItem newItem;
    private javax.swing.JMenuItem openItem;
    private javax.swing.JMenuItem quitItem;
    private javax.swing.JButton remove;
    private javax.swing.JMenuItem saveAsItem;
    private javax.swing.JMenuItem saveItem;
    private javax.swing.JList spriteList;
    private javax.swing.JLabel statusLabel;
    // End of variables declaration//GEN-END:variables


    class XMLFilter extends FileFilter {

        @Override
        public boolean accept(File f) {
            return f.isDirectory()
                    || f.getName().toLowerCase().endsWith(".xml");
        }

        @Override
        public String getDescription() {
            return "Sprite Library XML Files";
        }

    }
}
